# -*- coding: utf-8 -*-
import re
import codecs
import wx
import wx.stc as stc
import string

import format_py
import format_mel
import hoheWordCompleter

class PythonSTC(stc.StyledTextCtrl):
	def __init__(self, parent, ID, code, font, coding='utf_8'):
		self.hcompleter = hoheWordCompleter.HoheWordCompleter()
		self.code=code
		self.coding=coding
		
		stc.StyledTextCtrl.__init__(self, parent, ID,
								  style = wx.NO_FULL_REPAINT_ON_RESIZE)
		# [ Ctrl + B: Zoom ]
		self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
		self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
		
		if self.code == 'py':
			self.SetLexer(stc.STC_LEX_PYTHON)
			keywords = 	format_py.genKeywords()
			self.SetKeyWords(0, ' '.join(keywords))
			self.hcompleter.setKeywords(keywords)
			# Highlight tab/space mixing (shouldn't be any)
			self.SetProperty("tab.timmy.whinge.level", "0")
		elif self.code == 'mel':
			self.SetLexer(stc.STC_LEX_CPP)
			keywords = 	format_mel.genKeywords()
			self.SetKeyWords(0, ' '.join(keywords))
			self.hcompleter.setKeywords(keywords)
			
		# Enable folding
		self.SetProperty("fold", "1" )
		
		# Set left and right margins
		self.SetMargins(2,2)
		
		# Set up the numbers in the margin for margin #1
		self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
		# Reasonable value for, say, 4-5 digits using a mono font (40 pix)
		self.SetMarginWidth(1, 40)
		
		# Indentation and tab stuff
		self.SetIndent(4)				# Proscribed indent size for wx
		self.SetIndentationGuides(False) # Show indent guides
		self.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space
		self.SetTabIndents(True)		# Tab key indents
		self.SetTabWidth(4)				# Proscribed tab size for wx
		self.SetUseTabs(True)			# Use spaces rather than tabs, or
										# TabTimmy will complain!
		# White space
		#self.SetViewWhiteSpace(False)	# Don't view white space
		self.SetViewWhiteSpace(stc.STC_WS_VISIBLEALWAYS)
		self.SetWhitespaceBackground(1, '#F7F7D7')
		self.SetWhitespaceForeground(1, '#FFD494')
		
		# EOL: Since we are loading/saving ourselves, and the
		# strings will always have \n's in them, set the STC to
		# edit them that way.			 
		self.SetEOLMode(wx.stc.STC_EOL_LF)
		self.SetViewEOL(False)
		
		# No right-edge mode indicator
		self.SetEdgeMode(stc.STC_EDGE_NONE)
		
		# Setup a margin to hold fold markers
		self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
		self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
		self.SetMarginSensitive(2, True)
		self.SetMarginWidth(2, 12)
		
		# and now set up the fold markers
		self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,
						  stc.STC_MARK_BOXPLUSCONNECTED,
						  "white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID,
						  stc.STC_MARK_BOXMINUSCONNECTED,
						  "white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL,
						  stc.STC_MARK_TCORNER,	"white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,
						  stc.STC_MARK_LCORNER,	"white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,
						  stc.STC_MARK_VLINE,	"white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDER,
						  stc.STC_MARK_BOXPLUS,	"white", "black")
		self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,
						  stc.STC_MARK_BOXMINUS, "white", "black")
		
		stc.EVT_STC_UPDATEUI(self,	  ID, self.OnUpdateUI)
		stc.EVT_STC_MARGINCLICK(self, ID, self.OnMarginClick)
		stc.EVT_STC_USERLISTSELECTION(self, ID, self.OnCompletion)
		wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
		
		# Global default style
		self.SetFont(font)
		
	def SetFont(self, font):
		'Change basic font of editor. Callable from outside.'
		self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
						  ('fore:#000000,back:#F7F7D7,face:'
						   +font.GetFaceName()+',size:'
						   +str(font.GetPointSize())))
		self. __init2__()
		
	def __init2__(self):
		# Clear styles and revert to default.
		self.StyleClearAll()
		
		# Following style specs only indicate differences from default.
		# The rest remains unchanged.
		
		# Line numbers in margin
		self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,
						  'fore:#000000,back:#E7D4FA')
		# Highlighted brace
		self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,
						  'fore:#00009D,back:#FFFF00')
		# Unmatched brace
		self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,
						  'fore:#00009D,back:#FF0000')
		# Indentation guide
		self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE,
						  "fore:#CDCDCD")
		
		if self.code == 'py':
			format_py.setFont(self)
		elif self.code == 'mel':
			format_mel.setFont(self)
			
		selectionColour = '#50AA50'
		# Caret color
		self.SetCaretForeground(selectionColour)
		# Selection background
		self.SetSelBackground(1, '#66CCFF')
		
		self.SetSelBackground(True, selectionColour)
		self.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
		
		# Menu Events
		self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=wx.ID_UNDO)
		self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=wx.ID_REDO)
		self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=wx.ID_CUT)
		self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=wx.ID_COPY)
		self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=wx.ID_PASTE)
		self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=wx.ID_CLEAR)
		self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=wx.ID_SELECTALL)
		self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
	
	def GetContextMenu(self):
		menu = wx.Menu()
		menu.Append(wx.ID_UNDO, ("Undo"))
		menu.Append(wx.ID_REDO, ("Redo"))
		menu.AppendSeparator()
		menu.Append(wx.ID_CUT, ("Cut"))
		menu.Append(wx.ID_COPY, ("Copy"))
		menu.Append(wx.ID_PASTE, ("Paste"))
		menu.Append(wx.ID_CLEAR, ("Delete"))
		menu.AppendSeparator()
		menu.Append(wx.ID_SELECTALL, ("Select All"))
		
		# Disable if it doesn't work
		if self.GetSelectedText() == '':
			menu.Enable(wx.ID_CUT, False)
			menu.Enable(wx.ID_COPY, False)
			menu.Enable(wx.ID_CLEAR, False)
		if not self.CanUndo():
			menu.Enable(wx.ID_UNDO, False)
		if not self.CanRedo():
			menu.Enable(wx.ID_REDO, False)
			
		return menu
	
	def OnContextMenu(self, event):
		menu = self.GetContextMenu()
		self.PopupMenu(menu)
	
	def OnKeyPressed(self, event):
		if self.CallTipActive():
			self.CallTipCancel()
		key = event.KeyCode
		if key == 32 and event.ControlDown():
			pos = self.GetCurrentPos()
			# Tips
			if event.ShiftDown():
				self.CallTipSetBackground("yellow")
				self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
								 'show some suff, maybe parameters..\n\n'
								 'fubar(param1, param2)')
		elif key == wx.WXK_TAB and event.ControlDown():
			self.hcompleter.setText(self.GetText())
			linestr, linepos = self.GetCurLine()
			plinestr = linestr[:linepos]

			splitText = hoheWordCompleter.splitText(plinestr, 1)
			if not splitText:
				event.Skip()
				return

			partOfWord = splitText[-1]
			if partOfWord[-1] != plinestr[-1]:
				event.Skip()
				return
			candidates = self.hcompleter.getWords(partOfWord)
			self.partOfWord = partOfWord
			if len(candidates) == 0:
				# Nothing matched
				event.Skip()
			elif len(candidates) == 1:
				# Just apply directory
				self._autoComplete(candidates[0])
			else:
				# Show candidates list
				self.UserListShow(1, string.join(candidates))
		else:	
			event.Skip()
		
	def OnUpdateUI(self, evt):
		# check for matching braces
		braceAtCaret = -1
		braceOpposite = -1
		charBefore = None
		caretPos = self.GetCurrentPos()
		if caretPos > 0:
			charBefore = self.GetCharAt(caretPos - 1)
			styleBefore = self.GetStyleAt(caretPos - 1)
			
		# check before
		if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
			braceAtCaret = caretPos - 1
			
		# check after
		if braceAtCaret < 0:
			charAfter = self.GetCharAt(caretPos)
			styleAfter = self.GetStyleAt(caretPos)
			if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
				braceAtCaret = caretPos
				
		if braceAtCaret >= 0:
			braceOpposite = self.BraceMatch(braceAtCaret)
			
		if braceAtCaret != -1  and braceOpposite == -1:
			self.BraceBadLight(braceAtCaret)
		else:
			self.BraceHighlight(braceAtCaret, braceOpposite)
			#pt = self.PointFromPosition(braceOpposite)
			#self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
			#print pt
			#self.Refresh(False)
			
	def OnMarginClick(self, evt):
		# fold and unfold as needed
		if evt.GetMargin() == 2:
			if evt.GetShift() and evt.GetControl():
				self.FoldAll()
			else:
				lineClicked = self.LineFromPosition(evt.GetPosition())
				if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
					if evt.GetShift():
						self.SetFoldExpanded(lineClicked, True)
						self.Expand(lineClicked, True, True, 1)
					elif evt.GetControl():
						if self.GetFoldExpanded(lineClicked):
							self.SetFoldExpanded(lineClicked, False)
							self.Expand(lineClicked, False, True, 0)
						else:
							self.SetFoldExpanded(lineClicked, True)
							self.Expand(lineClicked, True, True, 100)
					else:
						self.ToggleFold(lineClicked)
						
	def OnCompletion(self, evt):
		word = evt.GetText()
		if word:
			self._autoComplete(word)
			
	def FoldAll(self):
		lineCount = self.GetLineCount()
		expanding = True
		
		# find out if we are folding or unfolding
		for lineNum in range(lineCount):
			if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
				expanding = not self.GetFoldExpanded(lineNum)
				break;
			
		lineNum = 0
		while lineNum < lineCount:
			level = self.GetFoldLevel(lineNum)
			if level & stc.STC_FOLDLEVELHEADERFLAG and \
			   (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
				
				if expanding:
					self.SetFoldExpanded(lineNum, True)
					lineNum = self.Expand(lineNum, True)
					lineNum = lineNum - 1
				else:
					lastChild = self.GetLastChild(lineNum, -1)
					self.SetFoldExpanded(lineNum, False)
					if lastChild > lineNum:
						self.HideLines(lineNum+1, lastChild)
						
			lineNum = lineNum + 1
			
	def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
		lastChild = self.GetLastChild(line, level)
		line = line + 1
		while line <= lastChild:
			if force:
				if visLevels > 0:
					self.ShowLines(line, line)
				else:
					self.HideLines(line, line)
			else:
				if doExpand:
					self.ShowLines(line, line)
					
			if level == -1:
				level = self.GetFoldLevel(line)
				
			if level & stc.STC_FOLDLEVELHEADERFLAG:
				if force:
					if visLevels > 1:
						self.SetFoldExpanded(line, True)
					else:
						self.SetFoldExpanded(line, False)
					line = self.Expand(line, doExpand, force, visLevels-1)
					
				else:
					if doExpand and self.GetFoldExpanded(line):
						line = self.Expand(line, True, force, visLevels-1)
					else:
						line = self.Expand(line, False, force, visLevels-1)
			else:
				line = line + 1;
				
		return line
	
	def detectEncoding(self, FirstLine, SecondLine):
		''' Detect character code from line.
		Expression can be...
		# encoding: utf-8
		# coding=euc-jp
		# -*- coding: euc-jp -*-(Emacs)
		# vim: fileencoding=euc-jp
		'''
		reObj = re.search('coding[ ]*[:|=][ ]*', FirstLine)
		if reObj:
			coding=FirstLine[reObj.end():].split(' ')[0]
		else:
			# Second Line
			reObj = re.search('coding[ ]*[:|=][ ]*', SecondLine)
			if reObj:
				coding=SecondLine[reObj.end():].split(' ')[0]
			else:
				return None
		return coding
	
	def getEncodingFromFile(self, filePath):
		coding = None
		f = open(filePath,'r')
		codingFromFile = self.detectEncoding(f.readline(), f.readline())
		f.close()
		return codingFromFile
	
	def LoadFile(self, filename, coding=''):
		"""
		Load File.
		"""
		if coding=='':
			# Detect a character code
			encodeFromFile=self.getEncodingFromFile(filename)
			if encodeFromFile:
				coding = encodeFromFile
			else:
				coding = self.coding
		
		# Load Fiile
		try:
			#print 'Load As ' + coding,
			self.SetText(codecs.open(filename, 'r', coding).read())
			return coding
			# Load File with Python can read UTF-8, UTF-8N
			# But Cannot read EUC, shift-JIS, Unicode.
		except UnicodeDecodeError:
			try:
				#print '...Failed. Use default function instead.'
				stc.StyledTextCtrl.LoadFile(self, filename)
				# Default loadFile can read EUC, shift-JIS, Unicode,
				# But error with UTF-8, UTF-8N.
			except UnicodeDecodeError:
				print 'Failed. Unknown character codec!'
		return 'utf_8'
	
	def SaveFile(self, filename, coding=''):
		"""
		Save File.
		Now it reads just second line and determine character code.
		"""
		codingFromFile = self.detectEncoding(self.GetLine(1), self.GetLine(2))
		if codingFromFile != None:
			coding = codingFromFile
		
		if coding == '':
			coding = self.coding
		
		fout = codecs.open(filename, 'w', coding)
		fout.write(self.GetText())
		fout.close()
		return True

	def _autoComplete(self, word):
		""" Apply selected word from autoComplete """
		insertString = word[len(self.partOfWord):]
		self.InsertText(self.GetCurrentPos(), insertString)
		newPosition = self.GetCurrentPos() + len(insertString)
		self.SetSelection(newPosition, newPosition)
